不知不覺來到了第28天,最後我們來做個複習吧。
用來解決非同步程式執行的問題,在以前面對非同步的程式時,我們可能需要建立一個新的執行緒,在這個執行緒上執行完耗時任務之後,接者再把結果由此執行緒傳回至原本的執行緒。
因為非同步程式的特性,我們需要使用 Callback 在耗時任務結束後把結果回傳回來,但是如果有好幾個 Callback 嵌套,也就是一個結果會傳至另一個函式作為參數,這樣就會產生所謂的 Callback hell(回調地獄),增加了維護的難度。
Coroutine 的目的就是要讓非同步的程式執行就像是同步的程式執行一樣,也就是說不需要 Callback 來接收不同執行緒的結果。
cancel()
只要呼叫 cancel()
該 Coroutine 的任務就會被取消。我們將耗時任務寫在 suspend 函式中,當程式執行到 suspend 函式時,將會暫停該 coroutine ,並且切換自動切換至其他的 coroutine,等到 suspend 函式完成它的任務時,就會在原本暫停的點繼續開始下面的任務。
suspend 函式只能在 coroutine scope 或是另外的 suspend 函式內呼叫。
要執行 suspend 函式,必須要在 CoroutineScope 中執行,而建立 Scope 的方式有兩種,一種是 launch
,另一種則是 async
。其中 launch 是沒有回傳值的(Side-effect),而 async 是有回傳值的。launch 回傳的是 Job,而 async 回傳的是 Deferred,其中 Deferred 也是繼承 Job 的類別。如前面所說,我們可以直接呼叫 Job 的 cancel() 取消 coroutine 的任務。
建立執行緒是需要較多資源,如果每次需要耗時任務都建立一個執行序,那麼是不太切實際的,所以Coroutine 中,使用者是不能直接使用執行緒的,這邊提供了幾種不同的 Dispatchers,使用者根據需求選擇不同的 Dispatchers,Coroutine 就會選擇適當的執行緒/執行緒池。這些執行緒/執行緒池是不會被關閉的,所以任務可以很快速的切換至不同的執行緒中。另外,如果要在 Coroutine 中切換不同的調度器,我們可以使用 withContext
。
我們可以在一個 Coroutine Scope 中執行多個 Job,它們彼此之間的關係是父-子的關係,也就是說,當 Coroutine Scope 裏面的所有 Job 結束之後,外層(父層)的 Coroutine 才能夠結束。如果其中一個子 Coroutine 發生錯誤被取消,那麼後面還沒有完成的任務也會一併被取消。另外,如果父 coroutine 被取消,那麼所有在裏面的子 Coroutine 也會一併被取消。這樣子的好處就是不會有父類別已經被取消,但是子 coroutine 卻還在執行,這樣子就有可能會發生 memory leak 的情況。
如果在一個 CoroutineScope 中,其中一個子 coroutine 被取消,如果我們使用的是 Job(),那麼後面的所有 job 都會被取消。但是如果使用的是 SupervisorJob(),當其中一個 Job 發生錯誤被取消時,後面的任務還是會繼續執行直到完成。
在 coroutine 中,我們一樣可以使用 try-catch 來將可能會發生例外(Exception) 的程式碼包起來,當發生例外的時候,就會被 try-catch 給攔截下來。如果我們希望能夠在 CoroutineScope 的範圍內能夠捕捉例外呢?我們可以使用 CoroutineExceptionHandler。不過 async
的例外會在呼叫 await()
時才會發生,所以如果要捕捉 async 的例外,則還是必須要在 await() 上使用 try-catch 。
呼,前十四天的內容就在這篇做個複習了,如果有什麼遺漏,或是有錯誤,麻煩請跟我說,下一篇將複習後面的部分: Channel、Flow、SharedFlow、StateFlow。
Kotlin Taiwan User Group
Kotlin 讀書會
有興趣的讀者歡迎參考:https://coroutine.kotlin.tips/
天瓏書局